package com.ea.admin.service.impl;

import com.ea.admin.mapper.FileInfoMapper;
import com.ea.admin.pojo.po.FileInfo;
import com.ea.admin.pojo.vo.file.FileInfoVo;
import com.ea.admin.service.FileService;
import com.ea.admin.utils.UnZipUtils;
import com.ea.common.result.BizException;
import com.ea.common.result.ErrorCode;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.apache.tomcat.util.http.fileupload.FileUtils.deleteDirectory;

@Service
public class FileServiceImpl implements FileService {
    @Resource
    FileInfoMapper fileInfoMapper;

    @Override
    public String getPathByType(String type) {
        ApplicationHome h = new ApplicationHome(getClass());
        File jarF = h.getSource();
        String tempPath = jarF.getParentFile().toString();
        if (!Objects.equals(tempPath, File.separator)) {
            tempPath += File.separator;
        }
        String rootPath = tempPath + "maven";
        switch (type) {
            case "root":
                return rootPath + File.separator;
            case "jpeg":
            case "png":
            case "jpg":
                return rootPath + File.separator + "upload"
                        + File.separator + "user" + File.separator
                        + "photo" + File.separator;
            case "avi":
            case "h264":
            case "mp4":
            case "webm":
                return rootPath + File.separator + "upload"
                        + File.separator + "user" + File.separator
                        + "video" + File.separator;
            case "xlsx":
            case "xls":
            case "zip":
            case "tar":
            case "gz":
            case "rar":
            case "7z":
                return rootPath + File.separator + "upload"
                        + File.separator + "other" + File.separator
                        + type + File.separator;
            case "sql":
                return rootPath + File.separator + "mysql"
                        + File.separator + "backup" + File.separator
                        + "sql" + File.separator;
            case "yml":
                return rootPath + File.separator + "admin"
                        + File.separator + "config" + File.separator;
            default:
                throw new BizException(ErrorCode.USER_PARAM_ERROR_FILE_NOT_SUPPORT);
        }
    }

    @Override
    public void uploadFile(MultipartFile file, String md5, String type) {
        if (file.isEmpty()) {
            // 用户没有上传照片，则忽略
            return;
        }

        try {
            String fileName = md5 + "." + type;
            String basePath = getPathByType(type) + md5;
            File file1 = new File(basePath);
            //如果目录不存在
            if (!(file1.exists())) {
                file1.mkdirs();
            }
            //将临时文件转存到电脑硬盘
            file.transferTo(new File(basePath + File.separator + fileName));
            // 将文件后缀存入mysql
            System.out.println("将文件后缀存入mysql");

            // LambdaQueryWrapper<FileInfo> wrapper = new LambdaQueryWrapper<>();
            // wrapper.eq(FileInfo::getMd5, md5);
            // wrapper.eq(FileInfo::getIsDel, 1);
            // FileInfo isDelFile = fileInfoMapper.selectOne(wrapper);
            // if (isDelFile != null) {
            //     // 文件已被删除，更新为 0
            //     isDelFile.setIsDel(0);
            //     fileInfoMapper.updateById(isDelFile);
            // } else {
            //     fileInfoMapper.insertFile(md5, type, file.getSize(), 0, 0);
            // }
            fileInfoMapper.insertFile(md5, type, file.getSize(), 0, 0);
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_UPLOAD_ERROR);
        }
    }

    @Override
    public void downloadFile(HttpServletResponse response, String md5) {
        //通过输入流读取文件内容
        try {
            FileInfo fileInfo = getFileInfoByMd5(md5);

            String type = fileInfo.getType();
            FileInputStream fileInputStream = new FileInputStream(getPathByType(type) + md5 + File.separator + md5 + "." + type);
            //通过输出流，将文件写回到浏览器，在浏览器展示图片
            ServletOutputStream outputStream = response.getOutputStream();

            switch (type) {
                case "jpg":
                case "jpeg":
                case "png":
                    response.setContentType("image/" + type);//设置响应回去的是什么类型的文件
                    break;
                case "avi":
                case "h264":
                case "mp4":
                case "webm":
                    response.setContentType("video/" + type);//设置响应回去的是什么类型的文件
                    break;
                case "xlsx":
                case "xls":
                    response.setContentType("application/vnd.ms-excel");//设置响应回去的是什么类型的文件
                    break;
                case "zip":
                case "tar":
                case "gz":
                case "rar":
                case "7z":
                    response.setContentType("application/octet-stream");//设置响应回去的是什么类型的文件
                    break;
                default:
                    throw new BizException(ErrorCode.USER_PARAM_ERROR_FILE_NOT_SUPPORT);
            }

            int length;       //   i/o读取
            byte[] bytes = new byte[1024];
            while (((length = fileInputStream.read(bytes)) != -1)) {
                outputStream.write(bytes, 0, length);
                outputStream.flush();
            }

            //关闭资源
            outputStream.close();
            fileInputStream.close();

        } catch (FileNotFoundException e) {
            throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_DOWNLOAD_ERROR);
        }
    }

    @Override
    public void uploadChunk(MultipartFile chunk, Integer index, Integer totalChunks, String md5, String type) {
        // 将文件后缀存入mysql
        fileInfoMapper.insertFile(md5, type, 0L, 0, 0);

        Path path = Paths.get(generatePath(getPathByType(type), md5, index, type));
        try {
            Files.write(path, chunk.getBytes());
        } catch (IOException e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_UPLOAD_ERROR);
        }
    }

    @Override
    public void downloadChunk(HttpServletResponse response, Integer index, String md5, Integer chunkSize) {

        FileInfo fileInfo = getFileInfoByMd5(md5);
        String type = fileInfo.getType();
        String fileName = getPathByType(type) + md5 + File.separator + md5 + "." + type;
        try {
            long offset = (long) chunkSize * (index - 1);
            byte[] chunk = getChunk(chunkSize, fileName, offset);
            response.addHeader("Access-Control-Allow-Origin", "Content-Disposition");
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setContentType("application/octet-stream");
            ServletOutputStream out;
            out = response.getOutputStream();
            out.write(chunk);
            out.flush();
            out.close();
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_DOWNLOAD_ERROR);
        }
    }

    private byte[] getChunk(Integer chunkSize, String fileName, long offset) {
        // 读取文件
        try {
            FileInputStream fileInputStream = new FileInputStream(fileName);
            fileInputStream.skip(offset);
            byte[] bytes = new byte[chunkSize];
            int length = fileInputStream.read(bytes);
            if (length == -1) {
                return new byte[0];
            }

            // 重新设置长度
            byte[] result = new byte[length];
            System.arraycopy(bytes, 0, result, 0, length);

            return result;
        } catch (IOException e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_DOWNLOAD_ERROR);
        }
    }


    @Override
    public void merge(String md5, String type) {
        // 从 mysql 中获得文件后缀
        if (type == null) {
            FileInfo fileInfo = getFileInfoByMd5(md5);
            type = fileInfo.getType();
        }
        // 视频文件上传完毕，从磁盘中将分块拼接起来
        mergeFile(md5, type);
        File file = new File(getPathByType(type) + md5 + File.separator + md5 + "." + type);

        fileInfoMapper.insertFile(md5, type, file.length(), 0, 0);
        fileInfoMapper.saveFile(md5, file.length(), 0, 0);
    }

    public MultipartFile convertFileToMultipartFile(File file) {
        FileInputStream inputStream;
        try {
            inputStream = new FileInputStream(file);
            return new MockMultipartFile(
                    "file",
                    file.getName(),
                    "APPLICATION_OCTET_STREAM",
                    inputStream);
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
        }
    }

    @Override
    public void updateMysqlFile(String filePath, String type, String fileUUID) {
        File file = new File(filePath + "." + type);
        // 获得文件大小
        long size = file.length();
        fileInfoMapper.insertFile(fileUUID, type, size, 0, 0);
        fileInfoMapper.saveFile(fileUUID, size, 0, 0);
    }

    @Override
    public String savePhotoFileFromBytes(byte[] file, String fileUUID, Integer index) {
        //创建文件流对象
        FileOutputStream fstream = null;
        String rootPath = getPathByType("jpeg");
        //从map中获取file对象
        String resultFilePath = rootPath + fileUUID + File.separator + fileUUID;
        // 去掉下标
        File saveFile = new File(resultFilePath + ".jpeg");
        //判断路径是否存在，不存在就创建
        if (!saveFile.getParentFile().exists()) {
            saveFile.getParentFile().mkdirs();
        }
        try {
            fstream = new FileOutputStream(saveFile, false);
            fstream.write(file);
            return resultFilePath;
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_UPLOAD_ERROR);
        } finally {
            if (fstream != null) {
                try {
                    fstream.flush();
                    fstream.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    @Override
    public List<Integer> getUploadedList(String md5) {
        String type = getTypeByMd5(md5);
        if (type == null) {
            return null;
        }

        List<Integer> list;

        String folder = getPathByType(type) + md5;
        try {
            list = Files.list(Paths.get(folder))
                    .filter(path -> !path.getFileName().toString().equals(md5 + "." + type))
                    .map(path -> {
                        String fileName = path.getFileName().toString();
                        int l = fileName.lastIndexOf("-");
                        int r = fileName.lastIndexOf(".");
                        return Integer.valueOf(fileName.substring(l + 1, r));
                    }).collect(Collectors.toList());
        } catch (IOException e) {
            // throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
            list = new ArrayList<>();
        }
        list.sort(Integer::compareTo);
        return list;
    }

    @Override
    public List<Integer> getUploadedList(String md5, String type) {
        List<Integer> list;

        String folder = getPathByType(type) + md5;
        try {
            list = Files.list(Paths.get(folder))
                    .filter(path -> !path.getFileName().toString().equals(md5 + "." + type))
                    .map(path -> {
                        String fileName = path.getFileName().toString();
                        int l = fileName.lastIndexOf("-");
                        int r = fileName.lastIndexOf(".");
                        return Integer.valueOf(fileName.substring(l + 1, r));
                    }).collect(Collectors.toList());
        } catch (IOException e) {
            // throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
            list = new ArrayList<>();
        }
        list.sort(Integer::compareTo);
        return list;
    }

    @Override
    public File getFileByMd5(String md5) {
        try {
            String type = getTypeByMd5(md5);
            if (type == null) {
                throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
            }

            System.out.println(getPathByType(type) + md5 + File.separator + md5 + "." + type);
            return new File(getPathByType(type) + md5 + File.separator + md5 + "." + type);

        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
        }
    }

    @Override
    public FileInfoVo getFileInfo(String md5) {
        FileInfo fileInfo = getFileInfoByMd5(md5);
        return new FileInfoVo(fileInfo.getMd5(), fileInfo.getSize(), fileInfo.getType());
    }

    @Override
    public String unZipByMd5(String md5) {
        FileInfo fileInfo = fileInfoMapper.selectById(md5);
        if (fileInfo == null) {
            throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
        }
        String type = fileInfo.getType();
        String fileName = getPathByType(type) + md5 + File.separator + md5 + "." + type;
        String folder = getPathByType(type) + md5;
        File file = new File(fileName);
        try {
            switch (type) {
                case "zip":
                    UnZipUtils.decompressZIP(file, folder, null);
                    return folder;
                case "tar":
                    UnZipUtils.decompressTAR(file, folder);
                    return folder;
                case "gz":
                    UnZipUtils.decompressGZ(file, folder);
                    return folder;
                case "rar":
                    UnZipUtils.decompressRAR(file, folder);
                    return folder;
                case "7z":
                    UnZipUtils.decompress7Z(file, folder);
                    return folder;
                default:
                    throw new BizException(ErrorCode.USER_PARAM_ERROR_FILE_NOT_SUPPORT);
            }
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_ZIP_PARSE_ERROR);
        }
    }

    @Override
    public String getTempPathByType(String type) {
        return getPathByType(type);
    }

    @Override
    public void saveTempFile(File tempFile, String tempFileMd5, String type) {
        // 修改文件目录名为 md5
        File file = new File(getPathByType(type) + tempFileMd5 + File.separator + tempFileMd5 + "." + type);
        File folder = new File(getPathByType(type) + tempFileMd5);

        if (fileExists(file.getAbsolutePath())) {
            return;
        }
        // 将errorFile转存入file
        try {
            if (!folder.exists()) {
                folder.mkdirs();
            }
            Files.copy(tempFile.toPath(), file.toPath());
        } catch (IOException e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_UPLOAD_ERROR);
        }
        // 删除errorFile
        tempFile.delete();

        fileInfoMapper.insertFile(tempFileMd5, type, tempFile.length(), 0, 0);
        fileInfoMapper.saveFile(tempFileMd5, tempFile.length(), 0, 0);
    }

    @Override
    public void deleteFileByMd5(String md5) {
        FileInfo fileInfo = getFileInfoByMd5(md5);
        String type = fileInfo.getType();
        String fileName = getPathByType(type) + md5 + File.separator + md5 + "." + type;
        File folder = new File(getPathByType(type) + md5);
        try {
            Files.delete(Paths.get(fileName));
            deleteDirectory(folder);
            fileInfoMapper.deleteById(md5);
        } catch (IOException e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_DELETE_ERROR);
        }
    }

    /**
     * 保存webm文件
     *
     * @return 不带后缀的文件路径
     */
    @Override
    public String saveWebmFileFromBytes(byte[] file, String fileUUID, Integer index) {
        //创建文件流对象
        FileOutputStream fstream = null;
        String rootPath = getPathByType("webm") + fileUUID;
        //从map中获取file对象
        String resultFilePath = rootPath + File.separator + fileUUID
                + "-" + index;
        File saveFile = new File(resultFilePath + ".webm");
        //判断路径是否存在，不存在就创建
        if (!saveFile.getParentFile().exists()) {
            saveFile.getParentFile().mkdirs();
        }
        try {
            fstream = new FileOutputStream(saveFile, false);
            fstream.write(file);
            return resultFilePath;
        } catch (Exception e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_UPLOAD_ERROR);
        } finally {
            if (fstream != null) {
                try {
                    fstream.flush();
                    fstream.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    private String getTypeByMd5(String md5) {
        FileInfo fileInfo = fileInfoMapper.selectById(md5);
        if (fileInfo == null) {
            return null;
        }
        return fileInfo.getType();
    }

    private FileInfo getFileInfoByMd5(String md5) {
        FileInfo fileInfo = fileInfoMapper.selectById(md5);
        if (fileInfo == null) {
            throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_FILE_NOT_EXISTS);
        }
        return fileInfo;
    }


    private void mergeFile(String md5, String type) {
        String fileName = getPathByType(type) + md5 + File.separator + md5 + "." + type;
        try {
            //先判断文件是否存在
            if (fileExists(fileName)) {
                //文件已存在
                return;
            }

            String folder = getPathByType(type) + md5;
            Files.createFile(Paths.get(fileName));
            Files.list(Paths.get(folder))
                    .filter(path -> !path.getFileName().toString().equals(md5 + "." + type))
                    .sorted((o1, o2) -> {
                        String p1 = o1.getFileName().toString();
                        String p2 = o2.getFileName().toString();
                        int l1 = p1.lastIndexOf("-");
                        int r1 = p1.lastIndexOf(".");
                        int l2 = p2.lastIndexOf("-");
                        int r2 = p2.lastIndexOf(".");
                        return Integer.valueOf(p2.substring(l2, r2)).compareTo(Integer.valueOf(p1.substring(l1, r1)));
                    })
                    .forEach(path -> {
                        try {
                            //以追加的形式写入文件
                            Files.write(
                                    Paths.get(fileName),
                                    Files.readAllBytes(path),
                                    StandardOpenOption.APPEND
                            );
                        } catch (IOException e) {
                            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_MERGE_ERROR);
                        }
                    });
        } catch (FileAlreadyExistsException e) {
            //文件已存在
        } catch (IOException e) {
            throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_MERGE_ERROR);
        }
    }

    /**
     * 生成块文件所在地址
     */
    private String generatePath(String uploadFolder, String md5, Integer index, String type) {
        StringBuilder sb = new StringBuilder();
        //文件夹地址/md5
        sb.append(uploadFolder).append(md5);
        //判断uploadFolder/identifier 路径是否存在，不存在则创建
        Path path = Paths.get(sb.toString());
        if (!Files.isWritable(path)) {
            try {
                Files.createDirectories(path);
            } catch (IOException e) {
                throw new BizException(ErrorCode.SYSTEM_ERROR_FILE_UPLOAD_ERROR);
            }
        }

        //文件夹地址/md5/md5-1.type
        return sb.append(File.separator)
                .append(md5).append("-").append(index)
                .append(".").append(type)
                .toString();
    }

    /**
     * 根据文件的全路径名判断文件是否存在
     */
    private boolean fileExists(String file) {
        boolean fileExists;
        Path path = Paths.get(file);
        fileExists = Files.exists(path, LinkOption.NOFOLLOW_LINKS);
        return fileExists;
    }
}
